﻿//
// Copyright (c) 2011 Canyala Innovation AB
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 
// associated documentation files (the "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
// copies of the Software, and to permit persons to whom the Software is furnished to do so, 
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH 
// THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
//

using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace Canyala.Lagoon.Engineering
{
    /// <summary>
    /// Represents a quantity that has a unit. [Fowler AP]
    /// </summary>
    public class Quantity
    {
        #region Invariants
        [ContractInvariantMethod]
        void Invariants()
        {
            Contract.Invariant(Prefix != null);
            Contract.Invariant(Unit != null);
        }
        #endregion

        internal double _amount;

        internal enum Operator { Add, Subtract, Multiply, Divide };

        /// <summary>
        /// Represents an Empty Dimension [Wolf NullObject]
        /// </summary>
        public static Quantity Empty = new Quantity(0);

        /// <summary>
        /// Creates a dimensionless unit.
        /// </summary>
        /// <param name="amount">The amount to initialize the quantity to.</param>
        public Quantity(double amount)
        {
            _amount = amount;
            Prefix = Prefix.Empty;
            Unit = Unit.Empty;
        }

        /// <summary>
        /// Creates a Dimension of a specific Unit.
        /// </summary>
        /// <param name="amount">The amount to initialize the quantity to.</param>
        /// <param name="unit">The unit the quantity represents.</param>
        public Quantity(double amount, Unit unit)
        {
            #region Requires
                Contract.Requires(unit != null);
            #endregion

            _amount = amount;
            Prefix = Prefix.FromPower((int)Math.Log10(amount));
            Unit = unit;
        }

        /// <summary>
        /// Creates a quantity of a specific prefixed unit.
        /// </summary>
        /// <param name="amount">The amount to initialize the quantity to, taking the prefix into account.</param>
        /// <param name="prefix">The prefix to use.</param>
        /// <param name="unit">The unit the quantity represents.</param>
        public Quantity(double amount, Prefix prefix, Unit unit)
        {
            #region Requires
            Contract.Requires<ArgumentNullException>(prefix != null, "prefix");
            Contract.Requires<ArgumentNullException>(unit != null, "unit");
            #endregion

            _amount = amount * prefix.Magnitude;
            Prefix = prefix;
            Unit = unit;
        }

        /// <summary>
        /// Provides the prefix of a quantity.
        /// </summary>
        public Prefix Prefix { get; private set; }
        /// <summary>
        /// Provides the unit of a quantity.
        /// </summary>
        public Unit Unit { get; private set; }

        /// <summary>
        /// Converts the quantity to a string.
        /// </summary>
        /// <returns>A string representing the quantity.</returns>
        public override string ToString()
        {
            return Unit.ToString(this);
        }

        /// <summary>
        /// Create a dimensionless quantity from a double.
        /// </summary>
        /// <param name="amount">The amount to assign to the Dimension.</param>
        /// <returns>A dimensionless quantity.</returns>
        public static implicit operator Quantity(double amount)
        {
            return new Quantity(amount);
        }

        /// <summary>
        /// Allows strings to be assigned to Quantities.
        /// </summary>
        /// <param name="text">The quantity in text form to assign to the quantity.</param>
        /// <returns>The quantity.</returns>
        public static implicit operator Quantity(string text)
        {
            #region Requires
                Contract.Requires(text != null);
            #endregion

            return Parse(text);
        }

        /// <summary>
        /// Adds two Quantities.
        /// </summary>
        /// <param name="a">The left hand side quantity.</param>
        /// <param name="b">The right hand side quantity.</param>
        /// <returns>The quantity sum.</returns>
        public static Quantity operator+(Quantity a, Quantity b)
        {
            return new Quantity(a._amount + b._amount, Unit.Resolve(a.Unit, b.Unit, Operator.Add));
        }

        /// <summary>
        /// Adds a quantity and a double.
        /// </summary>
        /// <param name="a">The left hand side quantity.</param>
        /// <param name="b">The right hand side double.</param>
        /// <returns>The quantity sum.</returns>
        public static Quantity operator +(Quantity a, double b)
        {
            return new Quantity(a._amount + b, a.Unit);
        }

        /// <summary>
        /// Adds a double and a quantity.
        /// </summary>
        /// <param name="a">The left hand side double.</param>
        /// <param name="b">The right hand side quantity.</param>
        /// <returns>The quantity sum.</returns>
        public static Quantity operator +(double a, Quantity b)
        {
            return new Quantity(a + b._amount, b.Unit);
        }

        /// <summary>
        /// Subtracts a Quantity from a Quantity
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static Quantity operator-(Quantity a, Quantity b)
        {
            return new Quantity(a._amount - b._amount, Unit.Resolve(a.Unit, b.Unit, Operator.Subtract));  
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static Quantity operator*(Quantity a, Quantity b)
        {
            return new Quantity(a._amount * b._amount, Unit.Resolve(a.Unit, b.Unit, Operator.Multiply));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static Quantity operator *(Quantity a, double factor)
        {
            return new Quantity(a._amount * factor, a.Unit);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static Quantity operator *(double factor, Quantity b)
        {
            return new Quantity(factor * b._amount, b.Unit);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static Quantity operator/(Quantity a, Quantity b)
        {
            return new Quantity(a._amount / b._amount, Unit.Resolve(a.Unit, b.Unit, Operator.Divide));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static Quantity operator /(Quantity a, double divisor)
        {
            return new Quantity(a._amount / divisor, a.Unit);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static bool operator ==(Quantity a, Quantity b)
        {
            if (a.Unit != b.Unit) throw new ArgumentException();
            return a._amount == b._amount;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static bool operator !=(Quantity a, Quantity b)
        {
            if (a.Unit != b.Unit) throw new ArgumentException();
            return a._amount != b._amount;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static bool operator >=(Quantity a, Quantity b)
        {
            if (a.Unit != b.Unit) throw new ArgumentException();
            return a._amount >= b._amount;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static bool operator <=(Quantity a, Quantity b)
        {
            if (a.Unit != b.Unit) throw new ArgumentException();
            return a._amount <= b._amount;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static bool operator >(Quantity a, Quantity b)
        {
            if (a.Unit != b.Unit) throw new ArgumentException();
            return a._amount > b._amount;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static bool operator <(Quantity a, Quantity b)
        {
            if (a.Unit != b.Unit) throw new ArgumentException();
            return a._amount < b._amount;
        }

        private static Regex parseTokenizer = new Regex(@"([^\s]+)\s+([^\s]+)", RegexOptions.Compiled);

        /// <summary>
        /// 
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        public static Quantity Parse(string s)
        {
            #region Requires
            Contract.Requires<ArgumentNullException>(s != null, "s");
            #endregion

            Match m = parseTokenizer.Match(s);
            return new Quantity(Double.Parse(m.Groups[1].Value), Prefix.FromText(m.Groups[2].Value), Unit.FromText(m.Groups[2].Value));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="s"></param>
        /// <param name="a"></param>
        /// <returns></returns>
        public static bool TryParse(string s, out Quantity a)
        {
            double d;
            if (Double.TryParse(s, out d))
            {
                a = new Quantity( d );
                return true;
            }
            else
            {
                a = new Quantity(0.0);
                return false;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public override bool Equals(object obj)
        {
            if (obj == null)
                return false;

            if (obj.GetType() != typeof(Quantity))
                return false;

            Quantity q = (Quantity) obj;
            if (q._amount != _amount)
                return false;

            if (q.Prefix != Prefix)
                return false;

            if (q.Unit != Unit)
                return false;

            return true;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            return ToString().GetHashCode();
        }
    }
}
